package com.managertrade.util;
import com.managertrade.dto.vo.KlineWithIndicators;

import java.util.List;

public class TechnicalIndicatorUtils {

    /** EMA 计算 */
    public static double calcEMA(List<KlineWithIndicators> klines, int period, int index, List<Double> emaList) {
        double k = 2.0 / (period + 1);
        if (index == 0) {
            emaList.add(klines.get(0).getClose());
            return emaList.get(0);
        } else {
            double ema = klines.get(index).getClose() * k + emaList.get(index - 1) * (1 - k);
            emaList.add(ema);
            return ema;
        }
    }

    /** MACD 计算 */
    public static double[] calcMACD(List<KlineWithIndicators> klines, int index,
                                    List<Double> emaShort, List<Double> emaLong,
                                    double[] deaCache) {
        double diff = emaShort.get(index) - emaLong.get(index);
        double dea = (index == 0 ? diff : deaCache[index - 1] * 0.8 + diff * 0.2);
        double bar = 2 * (diff - dea);
        deaCache[index] = dea;
        return new double[]{diff, dea, bar};
    }

    /** BOLL 计算 */
    public static double[] calcBOLL(List<KlineWithIndicators> klines, int period, double multiplier,
                                    int index, List<Double> middleCache) {
        if (index + 1 < period) return new double[]{0, 0, 0};
        double sum = 0;
        for (int i = index - period + 1; i <= index; i++) sum += klines.get(i).getClose();
        double ma = sum / period;
        middleCache.add(ma);
        double std = 0;
        for (int i = index - period + 1; i <= index; i++) {
            std += Math.pow(klines.get(i).getClose() - ma, 2);
        }
        std = Math.sqrt(std / period);
        return new double[]{ma + multiplier * std, ma, ma - multiplier * std};
    }

    /** 成交量均线 */
    public static double calcVolumeMA(List<KlineWithIndicators> klines, int period, int index, List<Double> volMaCache) {
        if (index + 1 < period) return 0;
        double sum = 0;
        for (int i = index - period + 1; i <= index; i++) sum += klines.get(i).getVolume();
        double ma = sum / period;
        volMaCache.add(ma);
        return ma;
    }

    /** RSI 计算 */
    public static double calcRSI(List<KlineWithIndicators> klines, int period, int index, List<Double> rsiCache) {
        if (index == 0) {
            rsiCache.add(50.0);
            return 50.0;
        }
        double gain = 0, loss = 0;
        int start = Math.max(1, index - period + 1);
        for (int i = start; i <= index; i++) {
            double diff = klines.get(i).getClose() - klines.get(i - 1).getClose();
            if (diff > 0) gain += diff;
            else loss -= diff;
        }
        double rs = loss == 0 ? 100 : gain / loss;
        double rsi = 100 - 100 / (1 + rs);
        rsiCache.add(rsi);
        return rsi;
    }

    /** KDJ 计算 */
    public static double[] calcKDJ(List<KlineWithIndicators> klines, int index, int kPeriod, int dPeriod, int jPeriod,
                                   double[] kCache, double[] dCache) {
        int start = Math.max(0, index - kPeriod + 1);
        double low = Double.MAX_VALUE, high = Double.MIN_VALUE;
        for (int i = start; i <= index; i++) {
            low = Math.min(low, klines.get(i).getLow());
            high = Math.max(high, klines.get(i).getHigh());
        }
        double rsv = high == low ? 50 : (klines.get(index).getClose() - low) / (high - low) * 100;
        double k = (index == 0 ? 50 : kCache[index - 1] * 2 / 3 + rsv / 3);
        double d = (index == 0 ? 50 : dCache[index - 1] * 2 / 3 + k / 3);
        double j = 3 * k - 2 * d;
        kCache[index] = k;
        dCache[index] = d;
        return new double[]{k, d, j};
    }

    /** ATR 计算 */
    public static double calcATR(List<KlineWithIndicators> klines, int period, int index, List<Double> atrCache) {
        if (index == 0) {
            atrCache.add(klines.get(0).getHigh() - klines.get(0).getLow());
            return atrCache.get(0);
        }
        double tr = Math.max(klines.get(index).getHigh() - klines.get(index).getLow(),
                Math.max(Math.abs(klines.get(index).getHigh() - klines.get(index - 1).getClose()),
                        Math.abs(klines.get(index).getLow() - klines.get(index - 1).getClose())));
        double atr = (atrCache.get(index - 1) * (period - 1) + tr) / period;
        atrCache.add(atr);
        return atr;
    }

    /** CCI 计算 */
    public static double calcCCI(List<KlineWithIndicators> klines, int period, int index, List<Double> cciCache) {
        if (index + 1 < period) return 0;
        double sum = 0;
        for (int i = index - period + 1; i <= index; i++) {
            double tp = (klines.get(i).getHigh() + klines.get(i).getLow() + klines.get(i).getClose()) / 3;
            sum += tp;
        }
        double ma = sum / period;
        double meanDeviation = 0;
        for (int i = index - period + 1; i <= index; i++) {
            double tp = (klines.get(i).getHigh() + klines.get(i).getLow() + klines.get(i).getClose()) / 3;
            meanDeviation += Math.abs(tp - ma);
        }
        double cci = (klines.get(index).getClose() - ma) / (0.015 * (meanDeviation / period));
        cciCache.add(cci);
        return cci;
    }

    /** OBV 计算 */
    public static double calcOBV(List<KlineWithIndicators> klines, int index, List<Double> obvCache) {
        if (index == 0) {
            obvCache.add(0.0);
            return 0.0;
        }
        double prevObv = obvCache.get(index - 1);
        double obv = prevObv + (klines.get(index).getClose() > klines.get(index - 1).getClose() ? klines.get(index).getVolume() :
                klines.get(index).getClose() < klines.get(index - 1).getClose() ? -klines.get(index).getVolume() : 0);
        obvCache.add(obv);
        return obv;
    }
}
